﻿namespace Refract.MVVM
{
    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows;
    using System.Windows.Threading;

    /// <summary>
    /// Base class for the view model, implements notification change and provides utility functionality
    /// </summary>
    public abstract class Component : INotifyPropertyChanged
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Component"/> class.
        /// </summary>
        static Component()
        {
            if (Deployment.Current != null)
            {
                Dispatcher = Deployment.Current.Dispatcher;
            }
        }

        #endregion Constructors

        #region Events

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion Events

        #region Properties

        /// <summary>
        /// Gets a value indicating whether the app is running in design mode.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this app is in design mode; otherwise, <c>false</c>.
        /// </value>
        public static bool IsInDesignMode
        {
            get
            {
                return DesignerProperties.IsInDesignTool;
            }
        }

        /// <summary>
        /// Gets or sets the dispatcher.
        /// </summary>
        /// <value>The dispatcher.</value>
        protected static Dispatcher Dispatcher
        {
            get; private set;
        }

        #endregion Properties

        #region Methods

        public static string GetVariableName<T>(T item)
            where T : class
        {
            //query the anonymous type for its properties
            var properties = typeof(T).GetProperties();

            //return the name of the first (and only) property, which is the Variable whose
            //name is requested
            return properties[0].Name;
        }

        public static object GetVariableValue<T>(T item)
            where T : class
        {
            //query the anonymous type for its properties
            var properties = typeof(T).GetProperties();

            //return the name of the first (and only) property, which is the Variable whose
            //name is requested
            return properties[0].GetValue(item, null);
        }

        /// <summary>
        /// Handles an exception, ensuring it is rethrown on UI thread.
        /// </summary>
        /// <param name="exception">The exception.</param>
        public static void HandleError(Exception exception)
        {
            if (Dispatcher != null && !Dispatcher.CheckAccess())
            {
                Dispatcher.BeginInvoke
                    (
                        (ThreadStart)(() => { throw exception; })
                    );
            }
            else
            {
                throw exception;
            }
        }

        /// <summary>
        /// Raises the property changed event.
        /// </summary>
        /// <param name="propertyName">Name of the property that changed.</param>
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged == null) return;

            if (Dispatcher != null && !Dispatcher.CheckAccess())
            {
                Dispatcher.BeginInvoke
                    (
                        (ThreadStart)(() => PropertyChanged(this, new PropertyChangedEventArgs(propertyName)))
                    );
            }
            else
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void RaisePropertyChanged<T>(T propertyContainer)
            where T : class
        {
            RaisePropertyChanged(GetVariableName(propertyContainer));
        }

        #endregion Methods
    }
}